import time
from urllib.parse import parse_qs, quote
from abc import ABC, abstractmethod
from typing import Dict, Optional, Any

import requests
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import JSONResponse
from jose import jwt, JWTError

from models.m import User
from tool.jwt_utils import create_access_token, create_refresh_token

dsf = APIRouter()


# ------------------------------
# 抽象接口：定义认证流程的各个环节
# ------------------------------
class AuthStrategy(ABC):
    """认证策略抽象基类，定义认证流程的标准接口"""

    @abstractmethod
    def get_auth_url(self) -> str:
        """获取第三方授权URL"""
        pass

    @abstractmethod
    def get_access_token(self, code: str) -> Dict[str, Any]:
        """通过授权码获取访问令牌"""
        pass

    @abstractmethod
    def get_user_info(self, access_token: str) -> Dict[str, Any]:
        """通过访问令牌获取用户信息"""
        pass


class UserHandler(ABC):
    """用户处理抽象基类，定义用户查询/创建的接口"""

    @abstractmethod
    async def get_or_create_user(self, user_info: Dict[str, Any]) -> User:
        """查询或创建用户"""
        pass


class TokenHandler(ABC):
    """Token处理抽象基类，定义Token生成接口"""

    @abstractmethod
    def create_tokens(self, user: User) -> Dict[str, str]:
        """生成访问令牌和刷新令牌"""
        pass


# ------------------------------
# 具体实现：钉钉认证相关
# ------------------------------
class DingTalkAuthStrategy(AuthStrategy):
    """钉钉认证策略实现"""

    def __init__(self, client_id: str, client_secret: str, redirect_uri: str):
        self.client_id = client_id
        self.client_secret = client_secret
        self.redirect_uri = redirect_uri

    def get_auth_url(self) -> str:
        """生成钉钉授权URL"""
        params = [
            f"redirect_uri={quote(self.redirect_uri)}",
            "response_type=code",
            f"client_id={self.client_id}",
            "scope=openid",
            "prompt=consent"
        ]
        return "https://login.dingtalk.com/oauth2/auth?" + "&".join(params)

    def get_access_token(self, code: str) -> Dict[str, Any]:
        """通过授权码获取钉钉访问令牌"""
        data = {
            "clientId": self.client_id,
            "clientSecret": self.client_secret,
            "code": code,
            "grantType": "authorization_code"
        }
        resp = requests.post(
            "https://api.dingtalk.com/v1.0/oauth2/userAccessToken",
            json=data
        ).json()
        if "accessToken" not in resp:
            raise HTTPException(status_code=400, detail="获取令牌失败：" + str(resp))
        return resp

    def get_user_info(self, access_token: str) -> Dict[str, Any]:
        """通过令牌获取钉钉用户信息"""
        headers = {"x-acs-dingtalk-access-token": access_token}
        resp = requests.get(
            "https://api.dingtalk.com/v1.0/contact/users/me",
            headers=headers
        ).json()
        if "nick" not in resp:
            raise HTTPException(status_code=400, detail="获取用户信息失败：" + str(resp))
        return resp


class DingTalkUserHandler(UserHandler):
    """钉钉用户处理实现（查询或创建系统用户）"""

    async def get_or_create_user(self, user_info: Dict[str, Any]) -> User:
        """根据钉钉用户信息查询或创建系统用户"""
        phone = user_info.get("mobile")
        name = user_info.get("nick")

        # 查询是否已存在该用户
        user = await User.filter(phone=phone).first()
        if not user:
            # 创建新用户
            user = await User.create(
                nickname=name,
                phone=phone,
                role=1,
                password_hash="123456"  # 第三方登录默认密码（实际应加密或无需密码）
            )
        return user


class DefaultTokenHandler(TokenHandler):
    """默认Token处理实现"""

    def create_tokens(self, user: User) -> Dict[str, str]:
        """生成访问令牌和刷新令牌"""
        token_data = {
            "user_id": user.id,
            "phone": user.phone,
            "nickname": user.nickname
        }
        return {
            "access_token": create_access_token(data=token_data),
            "refresh_token": create_refresh_token(data=token_data)
        }


# ------------------------------
# 工厂类：统一调度认证流程
# ------------------------------
class AuthFactory:
    """认证工厂，根据第三方类型创建对应的策略和处理器"""

    @staticmethod
    def get_auth_strategy(provider: str, **kwargs) -> AuthStrategy:
        """获取认证策略"""
        if provider == "dingtalk":
            return DingTalkAuthStrategy(
                client_id=kwargs.get("client_id"),
                client_secret=kwargs.get("client_secret"),
                redirect_uri=kwargs.get("redirect_uri")
            )
        raise ValueError(f"不支持的认证提供商：{provider}")

    @staticmethod
    def get_user_handler(provider: str) -> UserHandler:
        """获取用户处理器"""
        if provider == "dingtalk":
            return DingTalkUserHandler()
        raise ValueError(f"不支持的用户处理器：{provider}")

    @staticmethod
    def get_token_handler() -> TokenHandler:
        """获取Token处理器（默认实现）"""
        return DefaultTokenHandler()


# ------------------------------
# 接口路由：使用工厂模式实现
# ------------------------------
@dsf.get("/baidu")
async def get_dingtalk_auth_url():
    """获取钉钉授权URL（通过工厂模式创建策略）"""
    # 配置参数
    auth_config = {
        "client_id": "dingqxjco4n5jjtt7ctj",
        "client_secret": "cQJnGlcoMmz6Nnv-r0aFEpQAHqiwVK0t4yf1J_9Do8jQP1AV81iVT2M3GXHWLy53",
        "redirect_uri": "http://127.0.0.1:8000/user/dingding"
    }
    # 通过工厂获取钉钉认证策略
    auth_strategy = AuthFactory.get_auth_strategy(
        provider="dingtalk", **auth_config
    )
    auth_url = auth_strategy.get_auth_url()
    return JSONResponse({"url": auth_url})


@dsf.get("/dingding")
async def dingtalk_callback(request: Request):
    """钉钉回调处理（通过工厂模式完成完整认证流程）"""
    # 1. 解析回调参数中的授权码
    query_params = parse_qs(request.url.query)
    auth_code = query_params.get("code", [None])[0]
    if not auth_code:
        raise HTTPException(status_code=400, detail="缺少授权码")

    # 2. 初始化配置和工厂组件
    auth_config = {
        "client_id": "dingqxjco4n5jjtt7ctj",
        "client_secret": "cQJnGlcoMmz6Nnv-r0aFEpQAHqiwVK0t4yf1J_9Do8jQP1AV81iVT2M3GXHWLy53",
        "redirect_uri": "http://127.0.0.1:8000/user/dingding"
    }

    auth_strategy = AuthFactory.get_auth_strategy(provider="dingtalk", **auth_config)
    user_handler = AuthFactory.get_user_handler(provider="dingtalk")
    token_handler = AuthFactory.get_token_handler()

    try:
        # 3. 获取访问令牌
        token_info = auth_strategy.get_access_token(auth_code)
        access_token = token_info.get("accessToken")

        # 4. 获取用户信息
        user_info = auth_strategy.get_user_info(access_token)

        # 5. 查询或创建系统用户
        user = await user_handler.get_or_create_user(user_info)

        # 6. 生成Token
        tokens = token_handler.create_tokens(user)

        # 7. 返回结果
        return JSONResponse({
            "message": "登录成功",
            "status": "success",
            "data": {
                "token": tokens["access_token"],
                "refreshToken": tokens["refresh_token"],
                "user": {
                    "id": user.id,
                    "phone": user.phone,
                    "nickname": user.nickname,
                    "avatar_url": user.avatar_url,
                    "role": int(user.role),
                    "status": int(user.status)
                }
            }
        })
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"认证失败：{str(e)}")


@dsf.get("/refresh")
async def refresh_token(request: Request):
    """刷新Token接口（补充工厂模式思想）"""
    # 解析刷新令牌
    query_params = parse_qs(request.url.query)
    refresh_token = query_params.get("retoken", [None])[0]
    if not refresh_token:
        raise HTTPException(status_code=400, detail="缺少刷新令牌")

    try:
        # 解码刷新令牌（实际项目中应使用配置的密钥）
        secret_key = "your-secret-key"  # 建议从配置文件读取
        payload = jwt.decode(refresh_token, secret_key, algorithms=["HS256"])
    except JWTError:
        raise HTTPException(status_code=401, detail="无效的刷新令牌")

    # 生成新令牌（可进一步抽象为TokenHandler的方法）
    new_access_token = create_access_token(data={
        "user_id": payload["user_id"],
        "phone": payload["phone"],
        "nickname": payload["nickname"]
    })
    new_refresh_token = create_refresh_token(data={
        "user_id": payload["user_id"],
        "phone": payload["phone"],
        "nickname": payload["nickname"]
    })

    return JSONResponse({
        "code": 200,
        "token": new_access_token,
        "retoken": new_refresh_token,
        "user_id": payload["user_id"]
    })